/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.sword2;

import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Item;
import org.dspace.content.WorkspaceItem;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.workflow.WorkflowItem;
import org.swordapp.server.AuthCredentials;
import org.swordapp.server.ContainerManager;
import org.swordapp.server.Deposit;
import org.swordapp.server.DepositReceipt;
import org.swordapp.server.SwordAuthException;
import org.swordapp.server.SwordConfiguration;
import org.swordapp.server.SwordError;
import org.swordapp.server.SwordServerException;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

public class ContainerManagerDSpace extends DSpaceSwordAPI implements ContainerManager
{
	private static Logger log = Logger.getLogger(ContainerManagerDSpace.class);

	private VerboseDescription verboseDescription = new VerboseDescription();

	public boolean isStatementRequest(String editIRI, Map<String, String> accept, AuthCredentials authCredentials, SwordConfiguration swordConfig)
			throws SwordError, SwordServerException, SwordAuthException
	{
		SwordContext sc = null;
		try
		{
			sc = this.noAuthContext();
			SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;
			Context context = sc.getContext();

			String acceptContentType = this.getHeader(accept, "Accept", null);
			TreeMap<Float, List<String>> analysed = this.analyseAccept(acceptContentType);

			// a request is for a statement if the content negotiation asks for a format that the
			// Statement disseminator supports
			SwordStatementDisseminator disseminator = null;
			try
			{
				disseminator = SwordDisseminatorFactory.getStatementInstance(analysed);
			}
			catch (SwordError swordError)
			{
				// in this case, it means that no relevant disseminator could be found, which means
				// this is not a statement request
				return false;
			}
			return true;
		}
		catch (DSpaceSwordException e)
		{
			log.error("caught exception:", e);
            throw new SwordServerException("There was a problem determining the request type", e);
		}
		finally
        {
            // this is a read operation only, so there's never any need to commit the context
            if (sc != null)
            {
                sc.abort();
            }
        }
	}

	public DepositReceipt getEntry(String editIRI, Map<String, String> accept, AuthCredentials authCredentials, SwordConfiguration swordConfig)
			throws SwordServerException, SwordError, SwordAuthException
	{
		SwordContext sc = null;
		try
		{
			SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;
			sc = this.doAuth(authCredentials);
			Context context = sc.getContext();
			SwordUrlManager urlManager = config.getUrlManager(context, config);

			Item item = urlManager.getItem(context, editIRI);

			ReceiptGenerator genny = new ReceiptGenerator();
			DepositReceipt receipt = genny.createReceipt(context, item, config);
			return receipt;
		}
		catch (DSpaceSwordException e)
		{
			throw new SwordServerException(e);
		}
		finally
        {
            // this is a read operation only, so there's never any need to commit the context
            if (sc != null)
            {
                sc.abort();
            }
        }
	}

	public DepositReceipt replaceMetadata(String editIRI, Deposit deposit, AuthCredentials authCredentials, SwordConfiguration swordConfig)
			throws SwordError, SwordServerException, SwordAuthException
	{
		// start the timer
        Date start = new Date();

        // store up the verbose description, which we can then give back at the end if necessary
        this.verboseDescription.append("Initialising verbose replace of metadata");

		SwordContext sc = null;
        SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;

		try
        {
            sc = this.doAuth(authCredentials);
            Context context = sc.getContext();

            if (log.isDebugEnabled())
            {
                log.debug(LogManager.getHeader(context, "sword_replace", ""));
            }

            // get the deposit target
            Item item = this.getDSpaceTarget(context, editIRI, config);

			// now we have the deposit target, we can determine whether this operation is allowed
			// at all
			WorkflowManager wfm = WorkflowManagerFactory.getInstance();
			wfm.replaceMetadata(context, item);

            // find out if the supplied SWORDContext can submit to the given
            // dspace object
            SwordAuthenticator auth = new SwordAuthenticator();
            if (!auth.canSubmit(sc, item, this.verboseDescription))
            {
                // throw an exception if the deposit can't be made
                String oboEmail = "none";
                if (sc.getOnBehalfOf() != null)
                {
                    oboEmail = sc.getOnBehalfOf().getEmail();
                }
                log.info(LogManager.getHeader(context, "replace_failed_authorisation", "user=" +
                        sc.getAuthenticated().getEmail() + ",on_behalf_of=" + oboEmail));
                throw new SwordAuthException("Cannot replace the given item with this context");
            }

			// make a note of the authentication in the verbose string
            this.verboseDescription.append("Authenticated user: " + sc.getAuthenticated().getEmail());
            if (sc.getOnBehalfOf() != null)
            {
                this.verboseDescription.append("Depositing on behalf of: " + sc.getOnBehalfOf().getEmail());
            }

			DepositResult result = null;
			try
            {
				result = this.doReplaceMetadata(sc, item, deposit, authCredentials, config);
            }
            catch(DSpaceSwordException e)
            {
                if (config.isKeepPackageOnFailedIngest())
                {
                    try
                    {
                        this.storeEntryAsFile(deposit, authCredentials, config);
                    }
                    catch(IOException e2)
                    {
                        log.warn("Unable to store SWORD entry as file: " + e);
                    }
                }
                throw e;
            }
            catch(SwordError e)
            {
                if (config.isKeepPackageOnFailedIngest())
                {
                    try
                    {
                        this.storeEntryAsFile(deposit, authCredentials, config);
                    }
                    catch(IOException e2)
                    {
                        log.warn("Unable to store SWORD entry as file: " + e);
                    }
                }
                throw e;
            }

			// now we've produced a deposit, we need to decide on its workflow state
            wfm.resolveState(context, deposit, result, this.verboseDescription);

            ReceiptGenerator genny = new ReceiptGenerator();
            DepositReceipt receipt = genny.createReceipt(context, result, config);

            Date finish = new Date();
            long delta = finish.getTime() - start.getTime();

            this.verboseDescription.append("Total time for deposit processing: " + delta + " ms");
            receipt.setVerboseDescription(this.verboseDescription.toString());

            // if something hasn't killed it already (allowed), then complete the transaction
            sc.commit();

			return receipt;
		}
        catch (DSpaceSwordException e)
        {
            log.error("caught exception:", e);
            throw new SwordServerException("There was a problem depositing the item", e);
        }
        finally
        {
            // this is a read operation only, so there's never any need to commit the context
            if (sc != null)
            {
                sc.abort();
            }
        }
	}

	public DepositReceipt replaceMetadataAndMediaResource(String editIRI, Deposit deposit, AuthCredentials authCredentials, SwordConfiguration swordConfig)
			throws SwordError, SwordServerException, SwordAuthException
	{
		// start the timer
		Date start = new Date();

		// store up the verbose description, which we can then give back at the end if necessary
		this.verboseDescription.append("Initialising verbose multipart replace");

		SwordContext sc = null;
		SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;

		try
		{
			// first authenticate the request
			// note: this will build our various DSpace contexts for us
			sc = this.doAuth(authCredentials);
			Context context = sc.getContext();

			if (log.isDebugEnabled())
			{
				log.debug(LogManager.getHeader(context, "sword_create_new", ""));
			}

			// get the deposit target
            Item item = this.getDSpaceTarget(context, editIRI, config);

			// Ensure that this method is allowed
			WorkflowManager wfm = WorkflowManagerFactory.getInstance();
			wfm.replaceMetadataAndMediaResource(context, item);

			// find out if the supplied SWORDContext can submit to the given
			// dspace object
            SwordAuthenticator auth = new SwordAuthenticator();
			if (!auth.canSubmit(sc, item, this.verboseDescription))
			{
				// throw an exception if the deposit can't be made
				String oboEmail = "none";
				if (sc.getOnBehalfOf() != null)
				{
					oboEmail = sc.getOnBehalfOf().getEmail();
				}
				log.info(LogManager.getHeader(context, "deposit_failed_authorisation", "user=" +
						sc.getAuthenticated().getEmail() + ",on_behalf_of=" + oboEmail));
				throw new SwordAuthException("Cannot submit to the given collection with this context");
			}

			// make a note of the authentication in the verbose string
			this.verboseDescription.append("Authenticated user: " + sc.getAuthenticated().getEmail());
			if (sc.getOnBehalfOf() != null)
			{
				this.verboseDescription.append("Depositing on behalf of: " + sc.getOnBehalfOf().getEmail());
			}

			DepositResult result = null;
			try
			{
				result = this.replaceFromMultipart(sc, item, deposit, authCredentials, config);
			}
			catch(DSpaceSwordException e)
			{
				if (config.isKeepPackageOnFailedIngest())
				{
					try
					{
						this.storePackageAsFile(deposit, authCredentials, config);
						this.storeEntryAsFile(deposit, authCredentials, config);
					}
					catch(IOException e2)
					{
						log.warn("Unable to store SWORD package as file: " + e);
					}
				}
				throw e;
			}
			catch(SwordError e)
			{
				if (config.isKeepPackageOnFailedIngest())
				{
					try
					{
						this.storePackageAsFile(deposit, authCredentials, config);
						this.storeEntryAsFile(deposit, authCredentials, config);
					}
					catch(IOException e2)
					{
						log.warn("Unable to store SWORD package as file: " + e);
					}
				}
				throw e;
			}

            // now we've produced a deposit, we need to decide on its workflow state
            wfm.resolveState(context, deposit, result, this.verboseDescription);

			ReceiptGenerator genny = new ReceiptGenerator();
			DepositReceipt receipt = genny.createReceipt(context, result, config);

			Date finish = new Date();
			long delta = finish.getTime() - start.getTime();

			this.verboseDescription.append("Total time for deposit processing: " + delta + " ms");
			receipt.setVerboseDescription(this.verboseDescription.toString());

			// if something hasn't killed it already (allowed), then complete the transaction
			sc.commit();

			return receipt;
		}
		catch (DSpaceSwordException e)
		{
			log.error("caught exception:", e);
			throw new SwordServerException("There was a problem depositing the item", e);
		}
		finally
		{
			// this is a read operation only, so there's never any need to commit the context
			if (sc != null)
			{
				sc.abort();
			}
		}
	}

	public DepositReceipt addMetadataAndResources(String s, Deposit deposit, AuthCredentials authCredentials, SwordConfiguration config)
			throws SwordError, SwordServerException
	{
		return null;  //To change body of implemented methods use File | Settings | File Templates.
	}

	public DepositReceipt addMetadata(String editIRI, Deposit deposit, AuthCredentials authCredentials, SwordConfiguration swordConfig)
			throws SwordError, SwordServerException, SwordAuthException
	{
		// start the timer
        Date start = new Date();

        // store up the verbose description, which we can then give back at the end if necessary
        this.verboseDescription.append("Initialising verbose replace of metadata");

		SwordContext sc = null;
        SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;

		try
        {
            sc = this.doAuth(authCredentials);
            Context context = sc.getContext();

            if (log.isDebugEnabled())
            {
                log.debug(LogManager.getHeader(context, "sword_replace", ""));
            }

            // get the deposit target
            Item item = this.getDSpaceTarget(context, editIRI, config);

			// now we have the deposit target, we can determine whether this operation is allowed
			// at all
			WorkflowManager wfm = WorkflowManagerFactory.getInstance();
			wfm.addMetadata(context, item);

            // find out if the supplied SWORDContext can submit to the given
            // dspace object
            SwordAuthenticator auth = new SwordAuthenticator();
            if (!auth.canSubmit(sc, item, this.verboseDescription))
            {
                // throw an exception if the deposit can't be made
                String oboEmail = "none";
                if (sc.getOnBehalfOf() != null)
                {
                    oboEmail = sc.getOnBehalfOf().getEmail();
                }
                log.info(LogManager.getHeader(context, "replace_failed_authorisation", "user=" +
                        sc.getAuthenticated().getEmail() + ",on_behalf_of=" + oboEmail));
                throw new SwordAuthException("Cannot replace the given item with this context");
            }

			// make a note of the authentication in the verbose string
            this.verboseDescription.append("Authenticated user: " + sc.getAuthenticated().getEmail());
            if (sc.getOnBehalfOf() != null)
            {
                this.verboseDescription.append("Depositing on behalf of: " + sc.getOnBehalfOf().getEmail());
            }

			DepositResult result = null;
			try
            {
				result = this.doAddMetadata(sc, item, deposit, authCredentials, config);
            }
            catch(DSpaceSwordException e)
            {
                if (config.isKeepPackageOnFailedIngest())
                {
                    try
                    {
                        this.storeEntryAsFile(deposit, authCredentials, config);
                    }
                    catch(IOException e2)
                    {
                        log.warn("Unable to store SWORD entry as file: " + e);
                    }
                }
                throw e;
            }
            catch(SwordError e)
            {
                if (config.isKeepPackageOnFailedIngest())
                {
                    try
                    {
                        this.storeEntryAsFile(deposit, authCredentials, config);
                    }
                    catch(IOException e2)
                    {
                        log.warn("Unable to store SWORD entry as file: " + e);
                    }
                }
                throw e;
            }

			// now we've produced a deposit, we need to decide on its workflow state
            wfm.resolveState(context, deposit, result, this.verboseDescription);

            ReceiptGenerator genny = new ReceiptGenerator();
            DepositReceipt receipt = genny.createReceipt(context, result, config);

            Date finish = new Date();
            long delta = finish.getTime() - start.getTime();

            this.verboseDescription.append("Total time for deposit processing: " + delta + " ms");
            receipt.setVerboseDescription(this.verboseDescription.toString());

            // if something hasn't killed it already (allowed), then complete the transaction
            sc.commit();

			return receipt;
		}
        catch (DSpaceSwordException e)
        {
            log.error("caught exception:", e);
            throw new SwordServerException("There was a problem depositing the item", e);
        }
        finally
        {
            // this is a read operation only, so there's never any need to commit the context
            if (sc != null)
            {
                sc.abort();
            }
        }
	}

	public DepositReceipt addResources(String s, Deposit deposit, AuthCredentials authCredentials, SwordConfiguration config)
			throws SwordError, SwordServerException
	{
		return null;  //To change body of implemented methods use File | Settings | File Templates.
	}

	public void deleteContainer(String editIRI, AuthCredentials authCredentials, SwordConfiguration swordConfig)
			throws SwordError, SwordServerException, SwordAuthException
	{
		// start the timer
        Date start = new Date();

        // store up the verbose description, which we can then give back at the end if necessary
        this.verboseDescription.append("Initialising verbose container delete");

		SwordContext sc = null;
        SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;

		try
        {
            sc = this.doAuth(authCredentials);
            Context context = sc.getContext();

            if (log.isDebugEnabled())
            {
                log.debug(LogManager.getHeader(context, "sword_delete", ""));
            }

            // get the deposit target
            Item item = this.getDSpaceTarget(context, editIRI, config);

			// now we have the deposit target, we can determine whether this operation is allowed
			// at all
			WorkflowManager wfm = WorkflowManagerFactory.getInstance();
			wfm.deleteItem(context, item);

            // find out if the supplied SWORDContext can submit to the given
            // dspace object
            SwordAuthenticator auth = new SwordAuthenticator();
            if (!auth.canSubmit(sc, item, this.verboseDescription))
            {
                // throw an exception if the deposit can't be made
                String oboEmail = "none";
                if (sc.getOnBehalfOf() != null)
                {
                    oboEmail = sc.getOnBehalfOf().getEmail();
                }
                log.info(LogManager.getHeader(context, "replace_failed_authorisation", "user=" +
                        sc.getAuthenticated().getEmail() + ",on_behalf_of=" + oboEmail));
                throw new SwordAuthException("Cannot delete the given item with this context");
            }

			// make a note of the authentication in the verbose string
            this.verboseDescription.append("Authenticated user: " + sc.getAuthenticated().getEmail());
            if (sc.getOnBehalfOf() != null)
            {
                this.verboseDescription.append("Depositing on behalf of: " + sc.getOnBehalfOf().getEmail());
            }

			this.doContainerDelete(sc, item, authCredentials, config);

            Date finish = new Date();
            long delta = finish.getTime() - start.getTime();

            this.verboseDescription.append("Total time for deposit processing: " + delta + " ms");

            // if something hasn't killed it already (allowed), then complete the transaction
            sc.commit();
		}
        catch (DSpaceSwordException e)
        {
            log.error("caught exception:", e);
            throw new SwordServerException("There was a problem depositing the item", e);
        }
        finally
        {
            // this is a read operation only, so there's never any need to commit the context
            if (sc != null)
            {
                sc.abort();
            }
        }
	}

	public DepositReceipt useHeaders(String editIRI, Deposit deposit, AuthCredentials authCredentials, SwordConfiguration swordConfig)
			throws SwordError, SwordServerException, SwordAuthException
	{
		// start the timer
        Date start = new Date();

        // store up the verbose description, which we can then give back at the end if necessary
        this.verboseDescription.append("Initialising verbose empty request (headers only)");

		SwordContext sc = null;
        SwordConfigurationDSpace config = (SwordConfigurationDSpace) swordConfig;

		try
        {
            sc = this.doAuth(authCredentials);
            Context context = sc.getContext();

            if (log.isDebugEnabled())
            {
                log.debug(LogManager.getHeader(context, "sword_modify_by_headers", ""));
            }

            // get the deposit target
            Item item = this.getDSpaceTarget(context, editIRI, config);

			// now we have the deposit target, we can determine whether this operation is allowed
			// at all
			WorkflowManager wfm = WorkflowManagerFactory.getInstance();
			wfm.modifyState(context, item);

            // find out if the supplied SWORDContext can submit to the given
            // dspace object
            SwordAuthenticator auth = new SwordAuthenticator();
            if (!auth.canSubmit(sc, item, this.verboseDescription))
            {
                // throw an exception if the deposit can't be made
                String oboEmail = "none";
                if (sc.getOnBehalfOf() != null)
                {
                    oboEmail = sc.getOnBehalfOf().getEmail();
                }
                log.info(LogManager.getHeader(context, "modify_failed_authorisation", "user=" +
                        sc.getAuthenticated().getEmail() + ",on_behalf_of=" + oboEmail));
                throw new SwordAuthException("Cannot modify the given item with this context");
            }

			// make a note of the authentication in the verbose string
            this.verboseDescription.append("Authenticated user: " + sc.getAuthenticated().getEmail());
            if (sc.getOnBehalfOf() != null)
            {
                this.verboseDescription.append("Modifying on behalf of: " + sc.getOnBehalfOf().getEmail());
            }

			DepositResult result = new DepositResult();
			result.setItem(item);

			// the main objective here is just to resolve the state
            wfm.resolveState(context, deposit, result, this.verboseDescription);

			// now return the usual deposit receipt
            ReceiptGenerator genny = new ReceiptGenerator();
            DepositReceipt receipt = genny.createReceipt(context, item, config);

            Date finish = new Date();
            long delta = finish.getTime() - start.getTime();

            this.verboseDescription.append("Total time for modify processing: " + delta + " ms");
            receipt.setVerboseDescription(this.verboseDescription.toString());

            // if something hasn't killed it already (allowed), then complete the transaction
            sc.commit();

			return receipt;
		}
        catch (DSpaceSwordException e)
        {
            log.error("caught exception:", e);
            throw new SwordServerException("There was a problem depositing the item", e);
        }
        finally
        {
            // this is a read operation only, so there's never any need to commit the context
            if (sc != null)
            {
                sc.abort();
            }
        }
	}

	private DepositResult replaceFromMultipart(SwordContext swordContext, Item item, Deposit deposit, AuthCredentials authCredentials, SwordConfigurationDSpace swordConfig)
			throws DSpaceSwordException, SwordError, SwordAuthException, SwordServerException
	{
		// get the things out of the service that we need
		Context context = swordContext.getContext();
		SwordUrlManager urlManager = swordConfig.getUrlManager(swordContext.getContext(), swordConfig);

		// is the content acceptable?  If not, this will throw an error
        this.isAcceptable(swordConfig, context, deposit, item);

		// Obtain the relevant content ingester from the factory
		SwordContentIngester sci = SwordIngesterFactory.getContentInstance(context, deposit, item);
		this.verboseDescription.append("Loaded content ingester: " + sci.getClass().getName());

        // obtain the relevant entry intester from the factory
        SwordEntryIngester sei = SwordIngesterFactory.getEntryInstance(context, deposit, item);
		this.verboseDescription.append("Loaded entry ingester: " + sei.getClass().getName());

		try
		{
			// delegate the to the version manager to get rid of any existing content and to version
			// if if necessary
			VersionManager vm = new VersionManager();
			vm.emptyBundle(item, "ORIGINAL");
		}
		catch (SQLException e)
		{
			throw new DSpaceSwordException(e);
		}
		catch (AuthorizeException e)
		{
			throw new SwordAuthException(e);
		}
		catch (IOException e)
		{
			throw new DSpaceSwordException(e);
		}

		DepositResult result;
        if (swordConfig.isEntryFirst())
        {
            // do the entry deposit
            result = sei.ingest(context, deposit, item, this.verboseDescription, null, true);

            // do the content deposit
            result = sci.ingest(context, deposit, item, this.verboseDescription, result);
            this.verboseDescription.append("Archive ingest completed successfully");
        }
        else
        {
            // do the content deposit
            result = sci.ingest(context, deposit, item, this.verboseDescription, null);

            // do the entry deposit
            result = sei.ingest(context, deposit, item, this.verboseDescription, result, true);
            this.verboseDescription.append("Archive ingest completed successfully");
        }

        // store the originals (this code deals with the possibility that that's not required)
        this.storeOriginals(swordConfig, context, this.verboseDescription, deposit, result);

		return result;
	}

	private DepositResult doReplaceMetadata(SwordContext swordContext, Item item, Deposit deposit, AuthCredentials authCredentials, SwordConfigurationDSpace swordConfig)
			throws DSpaceSwordException, SwordError, SwordAuthException, SwordServerException
	{
		// get the things out of the service that we need
		Context context = swordContext.getContext();
		SwordUrlManager urlManager = swordConfig.getUrlManager(swordContext.getContext(), swordConfig);

		// Obtain the relevant ingester from the factory
		SwordEntryIngester si = SwordIngesterFactory.getEntryInstance(context, deposit, null);
		this.verboseDescription.append("Loaded ingester: " + si.getClass().getName());

		// do the deposit
		DepositResult result = si.ingest(context, deposit, item, this.verboseDescription, null, true);
		this.verboseDescription.append("Replace completed successfully");

		// store the originals (this code deals with the possibility that that's not required)
        this.storeOriginals(swordConfig, context, this.verboseDescription, deposit, result);

		return result;
	}

	protected DepositResult doAddMetadata(SwordContext swordContext, Item item, Deposit deposit, AuthCredentials authCredentials, SwordConfigurationDSpace swordConfig)
			throws DSpaceSwordException, SwordError, SwordAuthException, SwordServerException
	{
		return this.doAddMetadata(swordContext, item, deposit, authCredentials, swordConfig, null);
	}

	protected DepositResult doAddMetadata(SwordContext swordContext, Item item, Deposit deposit, AuthCredentials authCredentials,
										  SwordConfigurationDSpace swordConfig, DepositResult result)
			throws DSpaceSwordException, SwordError, SwordAuthException, SwordServerException
	{
		if (result == null)
		{
			result = new DepositResult();
		}

		// get the things out of the service that we need
		Context context = swordContext.getContext();
		SwordUrlManager urlManager = swordConfig.getUrlManager(swordContext.getContext(), swordConfig);

		// Obtain the relevant ingester from the factory
		SwordEntryIngester si = SwordIngesterFactory.getEntryInstance(context, deposit, null);
		this.verboseDescription.append("Loaded ingester: " + si.getClass().getName());

		// do the deposit
		result = si.ingest(context, deposit, item, this.verboseDescription, result, false);
		this.verboseDescription.append("Replace completed successfully");

		// store the originals (this code deals with the possibility that that's not required)
        this.storeOriginals(swordConfig, context, this.verboseDescription, deposit, result);

		return result;
	}

	protected void doContainerDelete(SwordContext swordContext, Item item, AuthCredentials authCredentials, SwordConfigurationDSpace swordConfig)
			throws DSpaceSwordException, SwordAuthException
	{
		try
		{
			Context context = swordContext.getContext();

			// first figure out if there's anything we need to do about the workflow/workspace state
			WorkflowTools wft = new WorkflowTools();
			if (wft.isItemInWorkspace(swordContext.getContext(), item))
			{
				WorkspaceItem wsi = wft.getWorkspaceItem(context, item);
				wsi.deleteAll();
			}
			else if (wft.isItemInWorkflow(context, item))
			{
				WorkflowItem wfi = wft.getWorkflowItem(context, item);
				wfi.deleteWrapper();
			}

			// then delete the item
			Collection[] collections = item.getCollections();
			for (Collection collection : collections)
			{
				collection.removeItem(item);
			}
		}
		catch (SQLException e)
		{
			throw new DSpaceSwordException(e);
		}
		catch (AuthorizeException e)
		{
			throw new SwordAuthException(e);
		}
		catch (IOException e)
		{
			throw new DSpaceSwordException(e);
		}
	}

	private Item getDSpaceTarget(Context context, String editUrl, SwordConfigurationDSpace config)
			throws DSpaceSwordException, SwordError
	{
		SwordUrlManager urlManager = config.getUrlManager(context, config);

		// get the target collection
		Item item = urlManager.getItem(context, editUrl);

		this.verboseDescription.append("Performing replace using edit-media URL: " + editUrl);
        this.verboseDescription.append("Location resolves to item with handle: " + item.getHandle());

		return item;
	}
}
